#include <stdlib.h>

#include "user_threads.h"
#include "user_threads_export.h"
#include "user_mutex.h"
#include "threads_queue.h"

#include <assert.h>

#define MAGIC_NUMBER 533145754

struct umutex_struct {
	/* equal to MAGIC_NUMBER if inited */
	unsigned long magic_number;

	/* the threads queue to lock() the mutex. the head is the current locking thread */
	struct tq * tq;

//	/* a mutex can be locked only once, even by the already locking thread */
//	bool is_locked;
//
//	/* to return different error code on lock() failure it it's the same thread locking already */
//	thread_id locking_thread;
};

/* internal mutex unlock, to enable the user_thread to unlock a specific thread's mutexes on exit() */
static int unlock_mutex(uthread_mutex_t mutex, thread_id thread);

/* internal lock() to call the user_thread's add to locking list */
static int aux_lock_mutex(uthread_mutex_t mutex, thread_id thread);

static bool is_mutex_initialized(uthread_mutex_t mutex);

/* returns the head of the thread queue */
static thread_id get_locking_thread(uthread_mutex_t mutex);

int uthread_mutex_init(uthread_mutex_t* mutex)
{
	int ret;

	*mutex = malloc(sizeof(**mutex));
	if (NULL == *mutex) {
		ret = MUTEX_FAILURE;
		goto error;
	}

	(*mutex)->tq = tq_init();
	if (NULL == (*mutex)->tq) {
		ret = MUTEX_FAILURE;
		goto error;
	}

	(*mutex)->magic_number = MAGIC_NUMBER;

	ret = MUTEX_SUCCESS;
	goto out;

error:
	free(*mutex);
out:
	return ret;
}


int uthread_mutex_destroy(uthread_mutex_t mutex)
{
	if (NULL == mutex) {
		return MUTEX_INVALID;
	}

	if (!is_mutex_initialized(mutex)){
		return MUTEX_UNINITIALIZED;
	}


	int ret;
	lock_t lock;
	mask_interrupts(&lock);

	if (!tq_is_empty(mutex->tq)) {
		ret = MUTEX_LOCKED;
		goto out;
	}

	tq_fini(mutex->tq);
	mutex->magic_number = 0;
	free(mutex);

	ret = MUTEX_SUCCESS;
out:
	unmask_interrupts(&lock);
	return ret;
}

int uthread_mutex_lock(uthread_mutex_t mutex)
{
	if (NULL == mutex) {
		return MUTEX_INVALID;
	}

	if (!is_mutex_initialized(mutex)){
		return MUTEX_UNINITIALIZED;
	}

	int ret = MUTEX_SUCCESS;

	lock_t lock;
	mask_interrupts(&lock);

	bool need_to_suspend = false;

	if (!tq_is_empty(mutex->tq)) {
		if (uthread_self() == get_locking_thread(mutex)) {
			ret = MUTEX_FAILURE;
			goto out;
		} else {
			need_to_suspend = true;
		}
	} else {
		int tmp = aux_lock_mutex(mutex,uthread_self());
		if (0 != tmp) {
			ret = MUTEX_FAILURE;
			goto out;
		}
	}

	if (need_to_suspend) {
		int tmp = tq_add(mutex->tq, uthread_self());
		if (0 != tmp) {
			ret = MUTEX_FAILURE;
			need_to_suspend = false;
		}
	}
out:
	if (need_to_suspend) {
		thread_id locking_thread = -1;
		_thread_mutex_wait(uthread_self(), mutex);
		suspend_uthread();
		unmask_interrupts(&lock);
		uthread_yield();
		mask_interrupts(&lock);
		/* since we are in a non-prioritized FIFO, once we were awoken, we should be the locking thread */
		assert(get_locking_thread(mutex) == uthread_self());
	}
	unmask_interrupts(&lock);
	return ret;
}

int uthread_mutex_try_lock(uthread_mutex_t mutex)
{

	if (NULL == mutex) {
		return MUTEX_INVALID;
	}

	if (!is_mutex_initialized(mutex)){
		return MUTEX_UNINITIALIZED;
	}

	int ret = MUTEX_SUCCESS;

	lock_t lock;
	mask_interrupts(&lock);

	if (!tq_is_empty(mutex->tq)) {
		if (uthread_self() == get_locking_thread(mutex)) {
			ret = MUTEX_FAILURE;
		} else {
			ret = MUTEX_LOCKED;
		}
	} else {
		int tmp = aux_lock_mutex(mutex,uthread_self());
		if (0 != tmp) {
			ret = MUTEX_FAILURE;
			goto out;
		}
	}
out:
	unmask_interrupts(&lock);

	return ret;
}

int uthread_mutex_unlock(uthread_mutex_t mutex)
{
	return unlock_mutex(mutex, uthread_self());
}

void _mutex_cleanup(thread_id t)
{
	/*
	  Note: we assume that the interrupts are currently masked (no
	  context-switch is possible).
	*/

	/* Dealing with the mutex that 't' is waiting for: */
	uthread_mutex_t m;
	m = _thread_peek_waiting(t);
	if (NULL != m) {
		int tmp = tq_remove_thread(m->tq,t);
		assert(0 == tmp);
	}

	/* Dealing with the mutexes that 't' is locking: */

	while (NULL != (m = _thread_peek_locking(t))){
		int tmp = unlock_mutex(m, t);
		assert (MUTEX_SUCCESS == tmp);
	}
}

static int unlock_mutex(uthread_mutex_t mutex, thread_id thread)
{
	int ret = MUTEX_INVALID;
	lock_t lock;
	mask_interrupts(&lock);

	if (NULL == mutex) {
		ret = MUTEX_INVALID;
		goto out;
	}

	if (!is_mutex_initialized(mutex)){
		ret = MUTEX_UNINITIALIZED;
		goto out;
	}

	if (tq_is_empty(mutex->tq)) {
		ret = MUTEX_UNLOCKED;
		goto out;
	}
	if (thread != get_locking_thread(mutex)) {
		ret = MUTEX_LOCKED;
		goto out;
	}

	thread_id removed_thread = -1;
	int tmp = tq_remove(mutex->tq, &removed_thread);
	assert(0 == tmp);
	assert(removed_thread == thread);

	/* This thread just released the lock */
	_thread_mutex_unlock(thread, mutex);

	if (!tq_is_empty(mutex->tq)) {
		_thread_mutex_clear_wait(get_locking_thread(mutex));
		wake_up_uthread(get_locking_thread(mutex));

		tmp = aux_lock_mutex(mutex, get_locking_thread(mutex));
		if (0 != tmp) {
			ret = MUTEX_FAILURE;
		}
	}
	ret = MUTEX_SUCCESS;

out:
	unmask_interrupts(&lock);
	return ret;
}


static int aux_lock_mutex(uthread_mutex_t mutex, thread_id thread)
{
	int tmp = _thread_mutex_lock(thread, mutex);
	if (0 != tmp) {
		return -1;
	}
	return 0;
}


static bool is_mutex_initialized(uthread_mutex_t mutex)
{
	return (MAGIC_NUMBER == mutex->magic_number);
}

static thread_id get_locking_thread(uthread_mutex_t mutex)
{
	assert(NULL != mutex);
	assert(is_mutext_initialized(mutex));
	assert(!tq_is_empty(mutex->tq));

	thread_id locking_thread = -1;
	tq_peek(mutex->tq, &locking_thread);
	return locking_thread;
}
